//////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2008-2010, Shane J. M. Liesegang
// All rights reserved.
// 
// Redistribution and use in source and binary forms, with or without 
// modification, are permitted provided that the following conditions are met:
// 
//     * Redistributions of source code must retain the above copyright 
//       notice, this list of conditions and the following disclaimer.
//     * Redistributions in binary form must reproduce the above copyright 
//       notice, this list of conditions and the following disclaimer in the 
//       documentation and/or other materials provided with the distribution.
//     * Neither the name of the copyright holder nor the names of any 
//       contributors may be used to endorse or promote products derived from 
//       this software without specific prior written permission.
// 
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
// POSSIBILITY OF SUCH DAMAGE.
//////////////////////////////////////////////////////////////////////////////

#include "Camera.h"
#include "Common.h"
#include "World.h"
#include "MathUtil.h"
#include "Switchboard.h"

#include <math.h>

Camera* Camera::s_Camera = NULL;

Camera::Camera()
{
	Reset();
}

Camera& Camera::GetInstance()
{
	if (s_Camera == NULL)
		s_Camera = new Camera();
	return *s_Camera;
}

// destroy the singleton
void Camera::Destroy()
{
	delete s_Camera;
	s_Camera = NULL;
}

void Camera::ResizeCallback(int w, int h)
{
	theCamera.Resize(w, h);
}

void Camera::Reset()
{
	_aperture = 90.0f;
	_position = Vector3(0.0f, 0.0f, 10.0f);
	_view = Vector3(0.0f, 0.0f, -10.0f);
	_up = Vector3(0.0f, 1.0f, 0.0f);
	_zNearClip = 0.001f;
	_zFarClip = 200.f;
}

void Camera::Resize(int width, int height)
{
	if ((_windowHeight != height) || (_windowWidth != width))
	{
		_windowHeight = height;
		_windowWidth = width;

		glViewport(0, 0, _windowWidth, _windowHeight);
	}

	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	gluPerspective(_aperture, (float)_windowWidth/(float)_windowHeight, _zNearClip, _zFarClip);
	glMatrixMode(GL_MODELVIEW);

	theSwitchboard.Broadcast(new Message("CameraChange"));
}

const int Camera::GetWindowWidth()
{
	return _windowWidth;
}

const int Camera::GetWindowHeight()
{
	return _windowHeight;
}

const double Camera::GetViewRadius()
{
	double sideAngle = MathUtil::ToRadians(_aperture / 2.0);
	return tan(sideAngle) * abs(_position.Z);
}

const Vector2 Camera::GetWorldMaxVertex()
{
	return MathUtil::ScreenToWorld(GetWindowWidth(), 0);
}

const Vector2 Camera::GetWorldMinVertex()
{
	return MathUtil::ScreenToWorld(0, GetWindowHeight());
}

void Camera::Render()
{
	/*
		TODO Make this so it only updates if things have been dirtied.
	 */
	
	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();
	gluLookAt(_position.X, _position.Y, _position.Z,
		_position.X + _view.X,
		_position.Y + _view.Y,
		_position.Z + _view.Z,
		_up.X, _up.Y, _up.Z
		);
}

void Camera::SetPosition(float x, float y, float z)
{
	_position = Vector3(x, y, z);
	theSwitchboard.Broadcast(new Message("CameraChange"));
}

void Camera::SetPosition(float x, float y)
{
        _position = Vector3(x, y, _position.Z);
        theSwitchboard.Broadcast(new Message("CameraChange"));
}

void Camera::SetPosition(Vector2 v2)
{
//      _position = Vector3(v2.X, v2.Y, 0);
        _position = Vector3(v2.X, v2.Y, _position.Z);
        theSwitchboard.Broadcast(new Message("CameraChange"));
}

void Camera::SetPosition(Vector3 v3)
{
        _position = v3;
        theSwitchboard.Broadcast(new Message("CameraChange"));
}

Vector2 Camera::GetPosition()
{
        return Vector2(_position.X, _position.Y);
}

float Camera::GetZ()
{
        return _position.Z;
}

float Camera::GetZForViewRadius(float radius)
{
        double sideAngle = MathUtil::ToRadians(_aperture / 2.0);
        return radius / tan(sideAngle);
}

float Camera::GetNearClipDist()
{
        return _zNearClip;
}

float Camera::GetFarClipDist()
{
        return _zFarClip;
}

void Camera::SetZByViewRadius(float newRadius)
{
        double sideAngle = MathUtil::ToRadians(_aperture / 2.0);
        _position.Z = newRadius / tan(sideAngle);
}

void Camera::SetNearClipDist(float dist)
{
        _zNearClip = dist;
        Resize(_windowWidth, _windowHeight);
}

void Camera::SetFarClipDist(float dist)
{
        _zFarClip = dist;
        Resize(_windowWidth, _windowHeight);
}

void Camera::SetViewCenter(float x, float y, float z)
{
        _view = Vector3(x, y, z);
        theSwitchboard.Broadcast(new Message("CameraChange"));
}

Vector3 Camera::GetViewCenter()
{
        return _view;
}
